/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package au.edu.swinburne.laurus;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Calendar;
import java.util.Date;
import java.util.Properties;
import java.util.ResourceBundle;

/**
 *
 * @author taz
 */
public class Laurus {

    static final String CONFIG_SERVICE_URL = "config.service.url";
    static final String CONFIG_REPORT_DIRECTORY = "config.report.directory";
    static final String CONFIG_CREDENTIALS = "config.credentials";
    static final String CONFIG_IDENTITY = "config.identity";
    static final String CONFIG_SHARED_SECRET = "config.share.secret";

    String startDate;
    String endDate;
    String configFile;

    /**
     * Generate and sign an authorization token that you can use to securely
     * communicate to iTunes U a user's credentials and identity. The token
     * includes all the data you need to communicate  to iTunes U as well as
     * a creation time stamp and a digital signature for the data and time.
     *
     * @param credentials The user's credentials string, as
     *                    obtained from getCredentialsString().
     * @param identity The user's identity string, as
     *                 obtained from getIdentityString().
     * @param time Token time stamp. The token will only be valid from
     *             its time stamp time and for a short time thereafter
     *             (usually 90 seconds).
     * @param key The bytes of your institution's iTunes U shared secret key.
     *
     * @return The authorization token. The returned token will
     *         be URL-encoded and can be sent to iTunes U with
     *         a <A HREF="http://www.ietf.org/rfc/rfc1866.txt">form
     *         submission</A>. iTunes U will typically respond with
     *         HTML that should be sent to the user's browser.
     */
    public String getAuthorizationToken(String credentials, String identity,
            Date time, byte[] key) {

        // Create a buffer with which to generate the authorization token.
        StringBuffer buffer = new StringBuffer();

        // Generate the authorization token.
        try {

            // Start with the appropriately encoded credentials.
            buffer.append("credentials=");
            buffer.append(URLEncoder.encode(credentials, "UTF-8"));

            // Add the appropriately encoded identity information.
            buffer.append("&identity=");
            buffer.append(URLEncoder.encode(identity, "UTF-8"));

            // Add the appropriately formatted time stamp. Note that
            // the time stamp is expressed in seconds, not milliseconds.
            buffer.append("&time=");
            buffer.append(time.getTime() / 1000);

            // Generate and add the token signature.
            String data = buffer.toString();
            buffer.append("&signature=");
            buffer.append(this.hmacSHA256(data, key));

        } catch (UnsupportedEncodingException e) {

            // UTF-8 encoding support is required.
            throw new java.lang.AssertionError(
                    "ITunesU.getAuthorizationToken(): " + "UTF-8 encoding not supported!");

        }

        // Return the signed authorization token.
        return buffer.toString();
    }

    /**
     * Generate the HMAC-SHA256 signature of a message string, as defined in
     * <A HREF="http://www.ietf.org/rfc/rfc2104.txt">RFC 2104</A>.
     *
     * @param message The string to sign.
     * @param key The bytes of the key to sign it with.
     *
     * @return A hexadecimal representation of the signature.
     */
    public String hmacSHA256(String message, byte[] key) {

        // Start by getting an object to generate SHA-256 hashes with.
        MessageDigest sha256 = null;
        try {
            sha256 = MessageDigest.getInstance("SHA-256");
        } catch (NoSuchAlgorithmException e) {
            throw new java.lang.AssertionError(
                    this.getClass().getName() + ".hmacSHA256(): SHA-256 algorithm not found!");
        }

        // Hash the key if necessary to make it fit in a block (see RFC 2104).
        if (key.length > 64) {
            sha256.update(key);
            key = sha256.digest();
            sha256.reset();
        }

        // Pad the key bytes to a block (see RFC 2104).
        byte block[] = new byte[64];
        for (int i = 0; i < key.length; ++i) {
            block[i] = key[i];
        }
        for (int i = key.length; i < block.length; ++i) {
            block[i] = 0;
        }

        // Calculate the inner hash, defined in RFC 2104 as
        // SHA-256(KEY ^ IPAD + MESSAGE)), where IPAD is 64 bytes of 0x36.
        for (int i = 0; i < 64; ++i) {
            block[i] ^= 0x36;
        }
        sha256.update(block);
        try {
            sha256.update(message.getBytes("UTF-8"));
        } catch (UnsupportedEncodingException e) {
            throw new java.lang.AssertionError(
                    "ITunesU.hmacSH256(): UTF-8 encoding not supported!");
        }
        byte[] hash = sha256.digest();
        sha256.reset();

        // Calculate the outer hash, defined in RFC 2104 as
        // SHA-256(KEY ^ OPAD + INNER_HASH), where OPAD is 64 bytes of 0x5c.
        for (int i = 0; i < 64; ++i) {
            block[i] ^= (0x36 ^ 0x5c);
        }
        sha256.update(block);
        sha256.update(hash);
        hash = sha256.digest();

        // The outer hash is the message signature...
        // convert its bytes to hexadecimals.
        char[] hexadecimals = new char[hash.length * 2];
        for (int i = 0; i < hash.length; ++i) {
            for (int j = 0; j < 2; ++j) {
                int value = (hash[i] >> (4 - 4 * j)) & 0xf;
                char base = (value < 10) ? ('0') : ('a' - 10);
                hexadecimals[i * 2 + j] = (char) (base + value);
            }
        }

        // Return a hexadecimal string representation of the message signature.
        return new String(hexadecimals);
    }

    public Laurus (String configFile, String startDate, String endDate) throws java.io.IOException, au.edu.swinburne.laurus.ConfigValidationException {
        this.configFile = configFile;
        this.startDate = startDate;
        this.endDate = endDate;
        init();
    }

    private void init () throws java.io.IOException, au.edu.swinburne.laurus.ConfigValidationException {
        File confFile = new File(this.configFile);
        if (!confFile.exists()) {
            createConfigFile(confFile);
        }
        validateConfig(confFile);
    }

    private boolean validateConfig (File confFile) throws au.edu.swinburne.laurus.ConfigValidationException {
        Properties props = new Properties();
        try {
            props.load(new FileInputStream(confFile));
            String credentials = props.getProperty(CONFIG_CREDENTIALS);
            if("".equals(credentials)) {
                throw new ConfigValidationException(CONFIG_CREDENTIALS + " is not set: " + confFile.getAbsolutePath());
            }
            String reportDirectory = props.getProperty(CONFIG_REPORT_DIRECTORY);
            if("".equals(reportDirectory)) {
                throw new ConfigValidationException(CONFIG_REPORT_DIRECTORY + " is not set: " + confFile.getAbsolutePath());
            }
            File rDir = new File(reportDirectory);
            if(!rDir.exists()) {
                if(!rDir.mkdir()) {
                    throw new ConfigValidationException(CONFIG_REPORT_DIRECTORY + " could not be created: " + confFile.getAbsolutePath());
                }
            }
            try {
                new URL(props.getProperty(CONFIG_SERVICE_URL));
            } catch (MalformedURLException murle) {
                throw new ConfigValidationException(CONFIG_SERVICE_URL + " is not a valid URL: " + confFile.getAbsolutePath());
            }
            String sharedSecret = props.getProperty(CONFIG_SHARED_SECRET);
            if("".equals(sharedSecret)) {
                throw new ConfigValidationException(CONFIG_SHARED_SECRET + " is not set: " + confFile.getAbsolutePath());
            }
        } catch (FileNotFoundException fnfe) {
            throw new ConfigValidationException("File not found: " + confFile.getAbsolutePath(), fnfe);
        } catch (IOException ioe) {
            throw new ConfigValidationException("IO error", ioe);
        }
        return true;
    }

    public String getEndDate () {
        return null;
    }

    public void setEndDate (String val) {
    }

    public String getStartDate () {
        return null;
    }

    public void setStartDate (String val) {
    }

    private void createConfigFile (File confFile) throws java.io.IOException {
        Properties props = new Properties();
        props.setProperty(CONFIG_CREDENTIALS, "");
        props.setProperty(CONFIG_IDENTITY, "");
        props.setProperty(CONFIG_REPORT_DIRECTORY, "");
        props.setProperty(CONFIG_SERVICE_URL, "");
        props.setProperty(CONFIG_SHARED_SECRET, "");
        props.store(new FileOutputStream(confFile), "");
    }

    public void downloadReport () throws ConfigValidationException, IOException {
        File confFile = new File(this.configFile);
        if(validateConfig(confFile)) {
            Properties props = new Properties();
            try {
                props.load(new FileInputStream(confFile));
            } catch (IOException ioe) {
                // should not happen because already validated the file.
            }
            String credentials = props.getProperty(CONFIG_CREDENTIALS);
            String identity = props.getProperty(CONFIG_IDENTITY);
            String reportDirectoryString = props.getProperty(CONFIG_REPORT_DIRECTORY);
            File reportDirectory = new File(reportDirectoryString);
            String sharedSecret = props.getProperty(CONFIG_SHARED_SECRET);
            String serviceUrlString = props.getProperty(CONFIG_SERVICE_URL);

            String authorisationToken = getAuthorizationToken(credentials, identity, Calendar.getInstance().getTime(), sharedSecret.getBytes());
            String urlString = serviceUrlString + "?StartDate=" + this.startDate
                    + "&EndDate=" + this.endDate + "&" + authorisationToken;
            System.out.println(urlString);

            URL url = null;
            try {
                url = new URL(urlString);
            } catch (MalformedURLException murle) {
                
            }
//            Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 1.1.4322)
            try {
                Thread.currentThread().sleep(30000);
            } catch (Exception e) {

            }
            URLConnection con = url.openConnection();
            con.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 1.1.4322)");
            InputStream is = (InputStream)con.getContent();
            File reportFile = new File(reportDirectory, "report_" + this.startDate + "_" + this.endDate + ".txt");
            FileOutputStream fos = new FileOutputStream(reportFile);
            int size = 0;
            byte[] buffer = new byte[10240];
            while((size = is.read(buffer)) > -1) {
                fos.write(buffer, 0, size);
            }
            fos.close();
            is.close();
        }
    }

    public static final ResourceBundle getResources() {
        return ResourceBundle.getBundle("au.edu.swinburne.laurus.config.Laurus");

    }
}
